package com.ksyun.campus.dataserver.services.impl;

import com.ksyun.campus.dataserver.domain.DataServer;
import com.ksyun.campus.dataserver.domain.FileType;
import com.ksyun.campus.dataserver.domain.ReplicaData;
import com.ksyun.campus.dataserver.domain.StatInfo;
import com.ksyun.campus.dataserver.dto.WriteReqDto;
import com.ksyun.campus.dataserver.dto.req.DataServerFixReqDto;
import com.ksyun.campus.dataserver.dto.resp.DataServerAllFilesRespDto;
import com.ksyun.campus.dataserver.dto.resp.ReadRespDto;
import com.ksyun.campus.dataserver.exception.FileNotFoundException;
import com.ksyun.campus.dataserver.exception.OperationFailedException;
import com.ksyun.campus.dataserver.services.DataService;
import com.ksyun.campus.dataserver.services.RegistService;
import com.ksyun.campus.dataserver.util.NetworkUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class DataServiceImpl implements DataService {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private RegistService registService;

    @Value("${storagePath}")
    private String STORAGE_PATH; // 数据存储根目录

    @Value("${server.port}")
    private int serverPort;  // 当前实例的端口号

    private static final int MAX_RETRIES = 1;  // 重试次数

    private final RestTemplate restTemplate;

    public DataServiceImpl() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(50000);  // 连接超时设置为5秒
        requestFactory.setReadTimeout(100000);    // 读取超时设置为10秒
        this.restTemplate = new RestTemplate(requestFactory);
    }

    /**
     * 写入数据到本地磁盘，并同步到指定副本的 dataServer
     * 首先会在指定路径下创建对应的文件夹或文件
     *
     * @param fileSystemName 文件系统名称，用于逻辑隔离
     * @param statInfo       文件元数据，包括文件路径、类型等信息
     * @param fileBytes      要写入的文件字节数据
     * @return 返回文件的最新状态信息（包含副本信息）
     */
    @Override
    public StatInfo write(String fileSystemName, StatInfo statInfo, byte[] fileBytes) {
        // 创建名为fileSystemName的专属文件夹
        try {
            Files.createDirectories(Paths.get(STORAGE_PATH + fileSystemName));
        } catch (IOException e) {
            throw new FileNotFoundException("fileSystemName文件夹创建失败 ");
        }

        String localIp = NetworkUtils.getLocalIp();
        String isolatedPath = fileSystemName + statInfo.getPath();
        String filePath = STORAGE_PATH + isolatedPath;
        File file = new File(filePath); // 这是属于本服务实例的路径

        boolean anyWriteFailed = false;
        for (ReplicaData replica : statInfo.getReplicaData()) {
            boolean success = false;
            int attempts = 0;

            while (attempts < MAX_RETRIES && !success) {
                try {
                    if (replica.getDsNode().equals(localIp + ":" + serverPort)) {
                        // 这两个校验检查只能在本地进行

                        // 检查文件父目录是否已存在（不存在则报错）
                        if (!file.getParentFile().exists()) {
                            log.error("文件父目录不存在: " + file.getParentFile());
                            throw new FileNotFoundException("文件父目录不存在: " + file.getParentFile());
                        }

                        //                        // 创建文件夹路径（如果不存在）
//                        if (!file.getParentFile().exists()) {
//                            try {
//                                Files.createDirectories(Paths.get(file.getParentFile().getAbsolutePath()));
//                                log.info("创建文件夹路径（如果不存在）");
//                            } catch (IOException e) {
//                                log.error("创建目录失败: " + file.getParentFile().getAbsolutePath(), e);
//                                throw new OperationFailedException("创建目录失败: " + file.getParentFile().getAbsolutePath());
//                            }
//                        }

//                        // 用这个会默认写入失败，删除已存在文件，还是在meta中拦截更好
//                        // 不用这段代码，重复写入为覆盖
//                        // 检查文件是否已存在（原为覆盖，这里是报错）
//                        if (file.exists()) {
//                            log.error("文件已存在: " + filePath);
//                            throw new FileAlreadyExistsException("文件已存在: " + filePath);
//                        }

                        // 判断文件类型
                        if (statInfo.getType() == FileType.Directory) {
                            // 如果是目录，创建目录
                            try {
                                Files.createDirectories(Paths.get(filePath));
                                log.info("目录已创建: " + filePath);
                                success = true;
                            } catch (IOException e) {
                                log.error("目录创建失败: " + filePath, e);
                                throw new OperationFailedException("目录创建失败: " + filePath);
                            }
                        } else if (statInfo.getType() == FileType.File) {
                            // 检查文件父目录是否已存在（不存在则报错）
                            if (!file.getParentFile().exists()) {
                                log.error("文件父目录不存在: " + file.getParentFile());
                                throw new FileNotFoundException("文件父目录不存在: " + file.getParentFile());
                            }

                            try (FileOutputStream fos = new FileOutputStream(filePath,true)) {   //, true
                                fos.write(fileBytes);
                                fos.flush();
                                success = true;
                            } catch (IOException e) {
                                log.error("本地写入失败: " + filePath, e);
                                throw new OperationFailedException("本地写入失败: " + filePath);
                            }
                        } else {
                            throw new OperationFailedException("不支持的文件类型: " + statInfo.getType());
                        }
                    } else {
                        String url = "http://" + replica.getDsNode() + "/data/write";
                        HttpHeaders headers = new HttpHeaders();
                        headers.add("fileSystemName", fileSystemName);

                        StatInfo newStatInfo = statInfo.toBuilder()
                                .path(statInfo.getPath())
                                .size(statInfo.getSize())
                                .mtime(statInfo.getMtime())
                                .type(statInfo.getType())
                                .replicaData(Arrays.asList(replica))
                                .build();

                        WriteReqDto writeReqDto = new WriteReqDto(newStatInfo, fileBytes, 0, fileBytes.length);
                        HttpEntity<WriteReqDto> requestEntity = new HttpEntity<>(writeReqDto, headers);

                        ResponseEntity<Void> response = restTemplate.exchange(
                                url, HttpMethod.POST, requestEntity, Void.class);
                        if (response.getStatusCode().is2xxSuccessful()) {
                            success = true;
                        }
                    }
                } catch (Exception e) {
                    attempts++;
                    log.error("写入失败，尝试次数: " + attempts + " | 节点: " + replica.getDsNode(), e);
                    try {
                        TimeUnit.SECONDS.sleep(2 ^ attempts);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        log.error("重试等待时被中断", ie);
                    }
                }
            }
            if (!success) {
                anyWriteFailed = true;
                break;
            }
        }
        if (anyWriteFailed) {
            // todo 已删除成功写入的文件 这里需要修改和校验
            delete(fileSystemName, statInfo);
            log.warn("写入失败，已删除成功写入的文件");
            throw new OperationFailedException("写入失败，已删除成功写入的部分文件");
        } else {
            log.info("成功写入所有副本");
            // 立即更新使用的容量
            ((RegistService) applicationContext.getBean(RegistService.class)).reportStatus();
            return statInfo;
        }
    }

    /**
     * 从本地磁盘读取指定路径的文件内容，并返回文件状态信息
     *
     * @param fileSystemName 文件系统名称，用于逻辑隔离
     * @param path           文件路径
     * @param offset         读取的偏移量
     * @param length         读取的长度
     * @param statInfo       文件元数据，包括文件路径、类型等信息
     * @return 返回文件内容
     */
    @Override
    public ReadRespDto read(String fileSystemName, String path, int offset, int length, StatInfo statInfo) {
        String isolatedPath = fileSystemName + path;
        String filePath = STORAGE_PATH + isolatedPath;
        File file = new File(filePath);

        // 检查文件是否已存在（不存在则报错）
        if (!file.exists()) {
            log.error("文件不存在: " + filePath);
            throw new FileNotFoundException("文件不存在: " + filePath);
        }

        if (length > statInfo.getSize()) {
            length = (int) statInfo.getSize();
        }

        byte[] buffer = new byte[length];
        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            raf.seek(offset);
            int readLength = raf.read(buffer, 0, length);

            return ReadRespDto.builder().data(buffer).length(readLength).build();
        } catch (IOException e) {
            log.error("读取文件失败: " + filePath, e);
            throw new OperationFailedException("读取文件失败: " + filePath);
        }
    }

    /**
     * 删除指定路径的文件或目录
     * 递归删除子文件和子目录
     *
     * @param fileSystemName 文件系统名称，用于逻辑隔离
     * @param statInfo       文件元数据，包括文件路径、类型等信息
     * @return 返回删除操作是否成功
     */
    @Override
    public boolean delete(String fileSystemName, StatInfo statInfo) {
        String localIp = NetworkUtils.getLocalIp();
        String isolatedPath = fileSystemName + statInfo.getPath();
        String filePath = STORAGE_PATH + isolatedPath;
        File file = new File(filePath);

        for (ReplicaData replica : statInfo.getReplicaData()) {
            if (replica.getDsNode().equals(localIp + ":" + serverPort)) {

//                // 检查文件是否已存在（不存在则报错）
//                if (!file.exists()) {
//                    log.error("文件不存在: " + filePath);
//                    throw new FileNotFoundException("文件不存在: " + filePath);
//                }

                // 递归删除目录或文件
                if (!deleteRecursively(file)) {
                    log.error("本地删除文件或目录失败: " + filePath);
                    throw new OperationFailedException("本地删除文件或目录失败: " + filePath);
                } else {
                    log.info("本地文件或目录已删除: " + filePath);
                }

            } else {
                try {
                    String url = "http://" + replica.getDsNode() + "/data/delete";
                    HttpHeaders headers = new HttpHeaders();
                    headers.add("fileSystemName", fileSystemName);

                    StatInfo newStatInfo = statInfo.toBuilder()
                            .replicaData(Arrays.asList(replica))
                            .build();

                    WriteReqDto writeReqDto = new WriteReqDto(newStatInfo, null, 0, 0);
                    HttpEntity<WriteReqDto> requestEntity = new HttpEntity<>(writeReqDto, headers);

                    restTemplate.postForObject(url, requestEntity, Void.class);
                } catch (Exception e) {
                    log.error("文件删除部分失败，无法访问节点: " + replica.getDsNode(), e);
                    throw new OperationFailedException("文件删除部分失败，无法访问节点: " + replica.getDsNode());
                }
            }
        }

        // 立即更新使用的容量
        ((RegistService) applicationContext.getBean(RegistService.class)).reportStatus();

        return true;
    }

    /**
     * 递归删除文件或目录
     *
     * @param file 要删除的文件或目录
     * @return 如果删除成功则返回true，否则返回false
     */
    private boolean deleteRecursively(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    if (!deleteRecursively(child)) {
                        return false; // 如果无法删除子文件/目录，则返回false
                    }
                }
            }
        }
        return file.delete(); // 删除文件或空目录
    }

    /**
     * 获取当前节点上的所有文件和目录信息
     * 递归遍历STORAGE_PATH路径，生成所有文件和目录的相对路径
     *
     * @return DataServerAllFilesRespDto 包含所有文件和目录信息的响应对象
     */
    @Override
    public DataServerAllFilesRespDto getAllFiles() {
        File storageDir = new File(STORAGE_PATH);
        List<String> fileList = new ArrayList<>();
        List<String> dirList = new ArrayList<>();

        // 调用递归方法填充文件和目录列表
        populateFileAndDirLists(storageDir, storageDir.getAbsolutePath(), fileList, dirList);

        return DataServerAllFilesRespDto.builder()
                .fileList(fileList)
                .dirList(dirList)
                .fileNumber(fileList.size())
                .dirNumber(dirList.size())
                .build();
    }

    /**
     * 递归遍历目录并填充文件和目录列表
     *
     * @param currentDir 当前目录
     * @param basePath   基础路径，用于计算相对路径
     * @param fileList   文件列表
     * @param dirList    目录列表
     */
    private void populateFileAndDirLists(File currentDir, String basePath, List<String> fileList, List<String> dirList) {
        if (currentDir.exists()) {
            File[] files = currentDir.listFiles();
            if (files != null) {
                for (File file : files) {
                    String relativePath = file.getAbsolutePath().substring(basePath.length()).replace("\\", "/");

                    if (file.isDirectory()) {
                        dirList.add(relativePath);
                        populateFileAndDirLists(file, basePath, fileList, dirList);
                    } else {
                        fileList.add(relativePath);
                    }
                }
            }
        }
    }

    /**
     * 获取当前节点的存储路径
     *
     * @return 当前节点的存储路径
     */
    @Override
    public String getStoragePath() {
        return STORAGE_PATH;
    }

    /**
     * 修复文件副本或文件夹
     * 从目标节点复制文件到当前节点，支持文件和文件夹修复
     *
     * @param fileSystemName 文件系统名称
     * @param fixReqDto      包含修复信息的请求数据传输对象
     * @return 修复是否成功
     */
    @Override
    public Boolean fixFiles(String fileSystemName, DataServerFixReqDto fixReqDto) {
        String remoteIp = fixReqDto.getExistFileList().getIp();
        int remotePort = fixReqDto.getExistFileList().getPort();
        String remoteUrl = "http://" + remoteIp + ":" + remotePort + "/data/getStoragePath";

        try {
            // Step 1: 获取目标节点的存储路径
            HttpHeaders headers = new HttpHeaders();
            HttpEntity<String> requestEntity = new HttpEntity<>(headers);
            ResponseEntity<String> responseEntity = restTemplate.exchange(remoteUrl, HttpMethod.GET, requestEntity, String.class);

            if (!responseEntity.getStatusCode().is2xxSuccessful()) {
                throw new OperationFailedException("无法从目标节点获取存储路径");
            }

            String d_storagePath = responseEntity.getBody();

            // Step 2: 使用传入的 fileSystemName 复制文件或文件夹到本实例的指定路径
            String sourcePath = d_storagePath + fileSystemName + fixReqDto.getPath();
            String destinationPath = STORAGE_PATH + fileSystemName + fixReqDto.getPath();

            File sourceFile = new File(sourcePath);
            File destinationFile = new File(destinationPath);

            if (!sourceFile.exists()) {
                log.error("源文件或文件夹不存在: " + sourcePath);
                throw new OperationFailedException("源文件或文件夹不存在: " + sourcePath);
            }

            // 如果是文件夹，递归复制整个文件夹
            if (sourceFile.isDirectory()) {
                copyDirectoryRecursively(sourceFile.toPath(), destinationFile.toPath());
            } else {
                // 否则，复制单个文件
                copyFile(sourceFile.toPath(), destinationFile.toPath());
            }

            log.info("文件或文件夹已成功从 {} 复制到 {}", sourcePath, destinationPath);
            return true;

        } catch (IOException e) {
            log.error("文件或文件夹复制失败", e);
            throw new OperationFailedException("文件或文件夹复制失败: " + e.getMessage());
        }
    }

    /**
     * 递归复制文件夹
     *
     * @param sourcePath      源文件夹路径
     * @param destinationPath 目标文件夹路径
     * @throws IOException 如果复制失败则抛出
     */
    private void copyDirectoryRecursively(Path sourcePath, Path destinationPath) throws IOException {
        Files.walk(sourcePath).forEach(source -> {
            Path destination = destinationPath.resolve(sourcePath.relativize(source));
            try {
                Files.createDirectories(destination.getParent());
                Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                throw new OperationFailedException("文件夹复制失败: " + e.getMessage());
            }
        });
    }

    /**
     * 复制单个文件
     *
     * @param sourcePath      源文件路径
     * @param destinationPath 目标文件路径
     * @throws IOException 如果复制失败则抛出
     */
    private void copyFile(Path sourcePath, Path destinationPath) throws IOException {
        Files.createDirectories(destinationPath.getParent());
        Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
    }

}



